#include "CCGLVideoRender.h"


CCGLVideoRender::CCGLVideoRender()
{
    m_pOpenGLVideoShader = std::unique_ptr<CCOpenGLShader>(new CCOpenGLShader());
}

CCGLVideoRender::~CCGLVideoRender()
{
}

void CCGLVideoRender::InitGL()
{
    m_bUpdateData = false;

    glClearColor(0.0, 0.0, 0.0, 1.0);
    // glClearDepthf(1.0); 是OpenGL中的一个函数调用，用于设置深度缓冲区在清除操作后的初始值。深度缓冲区（也称为Z缓冲区）用于存储每个像素的深度信息，以确定在渲染过程中哪些像素应该被绘制在前面，哪些应该被遮挡。
    // glClearDepthf 函数接受一个浮点数参数，这个参数指定了深度缓冲区清除后的初始深度值。在这个例子中，1.0 是传递给 glClearDepthf 的参数，它表示深度缓冲区将被清除为最大深度值。
    // 在OpenGL中，深度值通常是一个介于 0.0（近裁剪面）和 1.0（远裁剪面）之间的值。当深度缓冲区被清除为 1.0 时，所有像素的深度值都被设置为远裁剪面的深度，这意味着任何新绘制的像素都会通过深度测试，因为它们的深度值都会小于或等于深度缓冲区中的值。
    // 通常情况下，你会想要将深度缓冲区清除为 0.0，这样新绘制的像素就会从近裁剪面开始，并且只有当它们比深度缓冲区中的值更近时才会被绘制。然而，在某些特殊情况下，例如当你想要确保所有新绘制的像素都通过深度测试时，你可以将深度缓冲区清除为 1.0。
    // 请注意，glClearDepthf 设置的是清除操作的初始深度值，而实际的清除操作是通过调用 glClear(GL_DEPTH_BUFFER_BIT) 来执行的。
    glClearDepthf(1.0);
    // glEnable(GL_DEPTH_TEST); 是OpenGL中的一个函数调用，用于启用深度测试（也称为Z缓冲）。深度测试是OpenGL渲染过程中的一部分，用于确定哪些像素应该被绘制在屏幕上，哪些像素应该被遮挡。
    // 在视频渲染YUV数据时，启用深度测试可以帮助确保渲染的图像具有正确的深度信息，从而正确地处理场景中的遮挡关系。例如，如果一个物体在另一个物体的后面，深度测试可以确保后面的物体不会被错误地渲染在前面物体的上方。
    // 深度测试的工作原理如下：
    // 当OpenGL渲染一个场景时，它会为场景中的每个像素计算一个深度值，这个深度值通常表示像素在屏幕空间中的Z坐标。
    // 深度测试会检查新渲染的像素的深度值与当前存储在深度缓冲区中的深度值。
    // 如果新像素的深度值小于或等于深度缓冲区中的值，那么新像素会被绘制到屏幕上，否则它会被丢弃。
    // 通过这种方式，OpenGL可以确保只有最前面的像素会被绘制，而其他被遮挡的像素则不会被渲染。
    // 在视频渲染中，如果你有多个视频帧或者多个视频对象需要渲染，并且它们之间存在遮挡关系，启用深度测试可以帮助你正确地渲染这些视频帧或对象，确保它们按照正确的顺序和深度被绘制。
    // 这里不开启深度测试，因为开启深度测试后，将导致导致绘制完视频帧，再在视频帧上面绘制多边形，多边形无法显示
    glEnable(GL_DEPTH_TEST);
    // glDepthFunc(GL_LEQUAL); 是OpenGL中的一个函数调用，用于设置深度测试函数。深度测试是OpenGL渲染过程中的一部分，用于确定哪些像素应该被绘制在屏幕上，哪些像素应该被遮挡。
    // glDepthFunc 函数接受一个参数，这个参数指定了深度测试函数的类型。GL_LEQUAL 是一个常量，它表示深度测试函数将比较新绘制的像素的深度值与深度缓冲区中存储的深度值，并且只有当新像素的深度值小于或等于深度缓冲区中的值时，新像素才会被绘制。
    // 具体来说，GL_LEQUAL 深度测试函数的行为如下：
    // 如果新像素的深度值小于深度缓冲区中的值，新像素会被绘制。
    // 如果新像素的深度值等于深度缓冲区中的值，新像素也会被绘制。
    // 如果新像素的深度值大于深度缓冲区中的值，新像素不会被绘制。
    // GL_LEQUAL 是深度测试中最常用的函数之一，因为它允许绘制那些与深度缓冲区中像素具有相同深度值的像素，这对于大多数渲染场景都是合适的。如果你想要一个更严格的深度测试，可以使用 GL_LESS，它只允许绘制那些深度值小于深度缓冲区中值的像素。
    // 在设置深度测试函数之后，OpenGL会使用这个函数来决定是否绘制新像素。通过正确设置深度测试函数，你可以控制渲染的顺序和遮挡关系，从而创建出具有正确深度信息的3D场景。
    glDepthFunc(GL_LEQUAL);

    glGenTextures(3, m_textures);

    if (m_pAssetManager != NULL)
    {
        loadShaderResources(m_pAssetManager);
    }

    return;
}

void CCGLVideoRender::PaintGL()
{    
    // 如果你不使用深度测试，那么确实可以不传入 GL_DEPTH_BUFFER_BIT 到 glClear 函数中。
    // glClear 函数用于清除指定的缓冲区，而 GL_DEPTH_BUFFER_BIT 是一个标志位，用于指示深度缓冲区应该被清除。
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    if (m_bUpdateData == false)
    {
        return;
    }

    static CCVertex triangleVert[] = {
        {-1, 1, 1, 0, 0},
        {-1, -1, 1, 0, 1},
        {1, 1, 1, 1, 0},
        {1, -1, 1, 1, 1},
    };

    // 定义了一个4x4的浮点矩阵objectMat，用于存储对象的最终变换矩阵
    glm::mat4x4 objectMat;

    // 创建了一个变换矩阵objectTransMat，它将对象沿Z轴负方向平移3个单位。
    // glm::translate函数接受两个参数：一个单位矩阵（glm::mat4(1.0f)）和一个表示平移向量的glm::vec3。
    // 在这个例子中，平移向量是(0.0f, 0.0f, -3)，这意味着对象只在Z轴上移动。
    glm::mat4x4 objectTransMat = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -3));

    // glm::ortho 函数用于创建一个正交投影矩阵，它定义了一个矩形投影空间，其中所有物体的大小保持不变，没有透视效果。这个函数接受六个参数，它们定义了投影空间的边界。下面是每个参数的含义：
    // left: 指定投影空间的左边界。在这个例子中，-1.0 表示投影空间的左边界是屏幕空间的左侧。
    // right: 指定投影空间的右边界。在这个例子中，1.0 表示投影空间的右边界是屏幕空间的右侧。
    // bottom: 指定投影空间的下边界。在这个例子中，-1.0 表示投影空间的下边界是屏幕空间的底部。
    // top: 指定投影空间的上边界。在这个例子中，1.0 表示投影空间的上边界是屏幕空间的顶部。
    // near: 指定近裁剪面的距离。在这个例子中，0.1 表示近裁剪面距离观察者0.1个单位。任何在这个距离之前的物体都不会被渲染。
    // far: 指定远裁剪面的距离。在这个例子中，1000.0 表示远裁剪面距离观察者1000个单位。任何在这个距离之后的物体也不会被渲染。
    // 这些参数共同定义了一个矩形投影空间，其中left和right定义了水平方向的范围，bottom和top定义了垂直方向的范围，而near和far定义了深度方向的范围。
    glm::mat4 projMat = glm::ortho(-1.0, 1.0, -1.0, 1.0, 0.1, 1000.0);

    // 将投影矩阵projMat和平移矩阵objectTransMat相乘，并将结果赋值给objectMat。这个乘法操作将平移变换应用到投影空间中，得到对象的最终变换矩阵
    objectMat = projMat * objectTransMat;

    m_pOpenGLVideoShader->Bind();

    m_pOpenGLVideoShader->SetUniformValue("uni_mat", objectMat);

    m_pOpenGLVideoShader->EnableAttributeArray("attr_position");
    m_pOpenGLVideoShader->EnableAttributeArray("attr_uv");

    m_pOpenGLVideoShader->SetAttributeBuffer("attr_position", GL_FLOAT, triangleVert, 3, sizeof(CCVertex));
    m_pOpenGLVideoShader->SetAttributeBuffer("attr_uv", GL_FLOAT, &triangleVert[0].u, 2, sizeof(CCVertex));

    m_pOpenGLVideoShader->SetUniformValue("uni_textureY", 0);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, m_textures[0]);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_nVideoW, m_nVideoH, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pBufYuv420p.get());
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    m_pOpenGLVideoShader->SetUniformValue("uni_textureU", 1);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, m_textures[1]);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_nVideoW / 2, m_nVideoH / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, (char *)(m_pBufYuv420p.get() + m_yFrameLength));
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    m_pOpenGLVideoShader->SetUniformValue("uni_textureV", 2);
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, m_textures[2]);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_nVideoW / 2, m_nVideoH / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, (char *)(m_pBufYuv420p.get() + m_yFrameLength + m_uFrameLength));
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    m_pOpenGLVideoShader->DisableAttributeArray("attr_position");
    m_pOpenGLVideoShader->DisableAttributeArray("attr_uv");

    m_pOpenGLVideoShader->Release();

    return;
}

void CCGLVideoRender::RendVideo(YUVData_Frame * yuvFrame)
{
    if (yuvFrame == NULL)
    {
        return;
    }

    if (m_nVideoH != yuvFrame->height || m_nVideoW != yuvFrame->width)
    {
        m_pBufYuv420p.reset();
    }

    m_nVideoW = yuvFrame->width;
    m_nVideoH = yuvFrame->height;

    m_yFrameLength = yuvFrame->luma.length;
    m_uFrameLength = yuvFrame->chromaB.length;
    m_vFrameLength = yuvFrame->chromaR.length;

    // 申请内存存一帧yuv图像数据,其大小为分辨率的1.5倍
    int nLen = m_yFrameLength + m_uFrameLength + m_vFrameLength;

    if (!m_pBufYuv420p)
    {
        m_pBufYuv420p = std::unique_ptr<unsigned char[]>(new unsigned char[nLen]);
    }

    std::memcpy(m_pBufYuv420p.get(), yuvFrame->luma.dataBuffer.get(), m_yFrameLength);
    std::memcpy(m_pBufYuv420p.get() + m_yFrameLength, yuvFrame->chromaB.dataBuffer.get(), m_uFrameLength);
    std::memcpy(m_pBufYuv420p.get() + m_yFrameLength + m_uFrameLength, yuvFrame->chromaR.dataBuffer.get(), m_vFrameLength);

    m_bUpdateData = true;
}

void CCGLVideoRender::ResizeGL(int w, int h)
{
    glViewport(0, 0, w, h);
}

void CCGLVideoRender::SetupAssetManager(AAssetManager *pManager)
{
    m_pAssetManager = pManager;
}

void CCGLVideoRender::loadShaderResources(AAssetManager *pManager)
{
    m_pOpenGLVideoShader->InitShadersFromFile(pManager, "yuvplayv.glsl", "yuvplayf.glsl");
}
